#include <memory.h>
#include "buf.h"
#include "db.h"

//----------------------------------------
// Constructor of the class BufMgr
//----------------------------------------

BufMgr::BufMgr(const unsigned int bufs)
{
    /* Set number of pages in buffer */
    numBufs = bufs;
    
    /* Allocate space for buffer pool */
    bufPool = new Page[numBufs];
    bufTable = new BufDesc[numBufs];
    
    /* Reset clock hand (will start at 1, not 0)
     * TODO: Is this what we want?*/
    clockHand = numBufs-1;
}


BufMgr::~BufMgr() 
{
    /* flush out all dirty pages (pinned and unpinned) */
    for(unsigned int i = 0; i < numBufs; i++) {
        /* For all pages, write back data to page if dirty */
        if(bufTable[i].dirty) {
            /* TODO: do we need to return if error? can't return from destuctor... */
            bufTable[i].file->writePage(bufTable[i].pageNo, &bufPool[i]); 
           bufStats.diskwrites++;
        }
    }
    delete[] bufPool;
    delete[] bufTable;
}


const Status BufMgr::allocBuf(unsigned int & frame) 
{
   if(!numUnpinnedPages()) {
       return BUFFEREXCEEDED;
   } 
    
    while(true) {

        advanceClock();

        /* Check if current frame is valid */
        if(!bufTable[clockHand].valid) {
            /* Frame is good to use */
            break;
        }

        /* Check if loved, mark un-loved if so,
         * and advance clock */
        if(bufTable[clockHand].loved) {
            bufTable[clockHand].loved = false;
            continue;
        }

        /* Check if pinned, advance clock if it is */
        if(bufTable[clockHand].pinCnt > 0) {
            continue;
        }

        /* At this point, we have our frame to replace.
         * We have to erase old entry from bufMap. */
        bufMap.remove(bufTable[clockHand].file,bufTable[clockHand].pageNo); 
        
        break;
    }

    /* Flush the page if necessary */
    if(bufTable[clockHand].dirty) { 
        Status errStatus;
        errStatus = bufTable[clockHand].file->writePage(bufTable[clockHand].pageNo, &bufPool[clockHand]);
        if(errStatus != OK) return errStatus;
        bufStats.diskwrites++;
    }

    /* Prepare the frame for use */
    bufTable[clockHand].Clear();

    /* Return the allocated frame */
    frame = clockHand;

    return OK;
}

	
const Status BufMgr::readPage(File* file, const int PageNo, Page*& page)
{
    bufStats.accesses++;

    unsigned int frameNo;
    if(bufMap.lookup(file,PageNo,frameNo) == OK) {
        bufTable[frameNo].pinCnt++;    
        page = &bufPool[frameNo];  
        return OK;
    }

    Status errStatus;
    
    /* allocate a buffer frame */
    errStatus = allocBuf(frameNo);
    if(errStatus != OK) return errStatus;

    /* read page into the buffer pool frame from disk */
    page = &bufPool[frameNo];
    errStatus = file->readPage(PageNo,page);
    if(errStatus != OK) return errStatus;
    bufStats.diskreads++;

    errStatus = bufMap.insert(file,PageNo,frameNo);
    if(errStatus != OK) return errStatus;

    bufTable[frameNo].Set(file,PageNo); 

    return OK;
}


const Status BufMgr::unPinPage(File* file, const int PageNo, const bool dirty, const bool love) 
{
    Status errStatus;

    unsigned int frameNo;
    errStatus = bufMap.lookup(file,PageNo,frameNo);
    if(errStatus != OK) return errStatus;
   
    // check if pair (file, page) points to invalid buffer frame
    if(!bufTable[frameNo].valid) return BADBUFFER;
    
    // check if page is pinned; if so, decrement pin count
    if(bufTable[frameNo].pinCnt == 0) {
        return PAGENOTPINNED;
    }
    bufTable[frameNo].pinCnt--;

    // set book keeping bits
    if(dirty == true) {
        bufTable[frameNo].dirty = true;
    }
    if(love == true) {
        bufTable[frameNo].loved = true;
    }

    return OK;
}


const Status BufMgr::flushFile(File* file) 
{
    for(unsigned int i = 0; i < numBufs; i++) {
        if(bufTable[i].file == file && bufTable[i].pinCnt != 0) return PAGEPINNED;
    }

    for(unsigned int j = 0; j < numBufs; j++) {
        if(bufTable[j].file == file && bufTable[j].valid) {
            if(bufTable[j].dirty) {
                Status errStatus = bufTable[j].file->writePage(bufTable[j].pageNo,&bufPool[j]);
                if(errStatus != OK) return errStatus;
                bufStats.diskwrites++;
            }
            bufMap.remove(file,bufTable[j].pageNo);
            bufTable[j].Clear();
        }    
    }

    return OK;
}


const Status BufMgr::disposePage(File* file, const int pageNo)
{
    Status errStatus;
    
    // Check if in buffer pool
    unsigned int frameNo;
    errStatus = bufMap.lookup(file,pageNo,frameNo);
    if(errStatus == OK) {
        // Make sure page is not pinned
        if(bufTable[frameNo].pinCnt != 0) return PAGEPINNED;

        // Make sure page is valid
        if(!bufTable[frameNo].valid) return BADBUFFER;

        // Clear frame
        bufTable[frameNo].Clear();
    }

    // Deallocate page from file (can return BADPAGENO or UNIXERR)
    errStatus = file->disposePage(pageNo);
    bufStats.diskwrites++;
    if(errStatus != OK) return errStatus;

    return OK;
}


const Status BufMgr::allocPage(File* file, int& pageNo, Page*& page) 
{
    bufStats.accesses++;

    Status errStatus;

    // set up a frame to hold new page
    unsigned int frameNo;
    errStatus = allocBuf(frameNo);
    if(errStatus != OK) return errStatus;

    // allocate a new page in the specified file
    // return the assigned page number by reference    
    errStatus = file->allocatePage(pageNo); 
    if(errStatus != OK) return errStatus; 
    bufStats.diskwrites++;

    // add the new page to the map
    errStatus = bufMap.insert(file,pageNo,frameNo);
    if(errStatus != OK) return BUFMAPERROR;

    // setup the buffer frame for the newly added file
    bufTable[frameNo].Set(file,pageNo);

    page = &bufPool[frameNo];
    
    return OK;
}



// return the number of pages in the buffer pool that are not pinned
int BufMgr::numUnpinnedPages()
{
  int numFree = 0;
  BufDesc* tmpbuf;

  for (unsigned i=0; i<numBufs; i++) 
  {
    tmpbuf = &(bufTable[i]);
    if (tmpbuf->pinCnt == 0) numFree++;
  }

  return numFree;
}

void BufMgr::printSelf(void) {

  BufDesc* tmpbuf;
  
  cout << endl << "Print buffer...\n";
  for (unsigned i=0; i<numBufs; i++) {
    tmpbuf = &(bufTable[i]);
    cout << i << "\t" << (char*)(&bufPool[i]) 
      << "\tpinCnt: " << tmpbuf->pinCnt;
    
    if (tmpbuf->valid == true)
      cout << "\tvalid\n";
    cout << endl;
  };
}
